package utils

import (
	"fmt"
	"math"
	"sort"
	"strings"
	"time"
)

type CronExpItem struct {
	Min, Max        int
	Start, Interval int
	Vals            []int
	Val             int // -1:any

	CycleMin, CycleMax int
	UnitDuration       time.Duration // 单位时间

	NextValFunc func(t1 time.Time, v1 int) time.Time
}

func (this *CronExpItem) Reset() {
	this.NextValFunc = this.getNextVal_T0
}

func (this *CronExpItem) getNextVal_T0(t1 time.Time, v1 int) time.Time {
	return t1
}

/*
*

	最大到最小值 min-max
*/
func (this *CronExpItem) getNextVal_Boundary(t1 time.Time, v1 int) time.Time {
	if v1 < this.Min {
		return t1.Add(time.Duration(this.Min-v1) * this.UnitDuration)
	} else if v1 > this.Max {
		span := (this.CycleMax - this.CycleMin + 1 + this.Min) - v1
		return t1.Add(time.Duration(span) * this.UnitDuration)
	}
	return t1
}

/*
*

	vals
*/
func (this *CronExpItem) getNextVal_Vals(t1 time.Time, v1 int) time.Time {
	v0 := this.Vals[0]
	if v0 == v1 {
		return t1
	}

	if v0 > v1 {
		return t1.Add(time.Duration(v0-v1) * this.UnitDuration)
	}

	// 从vals中查找
	for i := 1; i < len(this.Vals); i++ {
		v := this.Vals[i]
		if v >= v1 {
			return t1.Add(time.Duration(v-v1) * this.UnitDuration)
		}
	}

	span := (this.CycleMax - this.CycleMin + 1 + v0) - v1
	return t1.Add(time.Duration(span) * this.UnitDuration)
}

/*
*

	start/interval
*/
func (this *CronExpItem) getNextVal_Interval(t1 time.Time, v1 int) time.Time {
	v0 := this.Start
	if v0 == v1 {
		return t1
	}

	if v0 > v1 {
		return t1.Add(time.Duration(v0-v1) * this.UnitDuration)
	}

	v2 := int(math.Ceil(float64(v1-this.Start)/float64(this.Interval)))*this.Interval + this.Start
	if v2 <= this.CycleMax {
		return t1.Add(time.Duration(v2-v1) * this.UnitDuration)
	}

	span := (this.CycleMax - this.CycleMin + 1 + v0) - v1
	return t1.Add(time.Duration(span) * this.UnitDuration)
}

func (this *CronExpItem) getNextVal_Val(t1 time.Time, v1 int) time.Time {
	if this.Val == -1 { // any
		return t1
	}
	if this.Val == v1 {
		return t1
	}

	if this.Val > v1 {
		return t1.Add(time.Duration(this.Val-v1) * this.UnitDuration)
	}

	span := (this.CycleMax - this.CycleMin + 1 + this.Val) - v1
	return t1.Add(time.Duration(span) * this.UnitDuration)
}

func ParseExpItem(exp *CronExpItem, s string, cyclemin, cyclemax int, unitDuration time.Duration) error {
	if s == "*" || len(s) == 0 {
		exp.Reset()
		return nil
	}
	exp.CycleMax = cyclemax
	exp.CycleMin = cyclemin
	exp.UnitDuration = unitDuration
	s1, s2 := Split2Str(s, "-")
	if len(s2) > 0 {
		if !CronExpGetIntVal(Trim(s1), &exp.Min) {
			return fmt.Errorf("exp parse boundary err[%s]", s)
		}

		if !CronExpGetIntVal(Trim(s2), &exp.Max) {
			return fmt.Errorf("exp parse boundary err[%s]", s)
		}

		if exp.Min < exp.CycleMin || exp.Max > exp.CycleMax {
			return fmt.Errorf("exp parse boundary(%d..%d) err[%s]", exp.CycleMin, exp.CycleMax, s)
		}
		exp.NextValFunc = exp.getNextVal_Boundary
		return nil
	}

	{
		strs := strings.Split(s, ",")
		if len(strs) >= 2 {
			var v int
			exp.Vals = make([]int, 0, len(strs))
			for i := 0; i < len(strs); i++ {
				if !CronExpGetIntVal(Trim(strs[i]), &v) {
					return fmt.Errorf("exp parse elem err[%s]", s)
				}
				exp.Vals = append(exp.Vals, v)
			}
			sort.Sort(sort.IntSlice(exp.Vals))
			exp.NextValFunc = exp.getNextVal_Vals
			return nil
		}
	}

	{
		s1, s2 := Split2Str(s, "/")
		if len(s2) > 0 {
			if !CronExpGetIntVal(Trim(s1), &exp.Start) {
				return fmt.Errorf("exp parse start/interval err[%s]", s)
			}

			if !CronExpGetIntVal(Trim(s2), &exp.Interval) {
				return fmt.Errorf("exp parse start/interval err[%s]", s)
			}

			if exp.Start < exp.CycleMin || exp.Interval > exp.CycleMax {
				return fmt.Errorf("exp parse boundary(%d..%d) err[%s]", exp.CycleMin, exp.CycleMax, s)
			}
			exp.NextValFunc = exp.getNextVal_Interval
			return nil
		}
	}

	if !CronExpGetIntVal(Trim(s), &exp.Val) {
		return fmt.Errorf("exp parse val err[%s]", s)
	} else {
		exp.NextValFunc = exp.getNextVal_Val
	}

	return nil
}
